package com.shiguiwu.springmybatis.spring.aop.principle.factory;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.AopInvocationException;
import org.springframework.aop.RawTargetAccess;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.DecoratingProxy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

/**
 * @description:
 * @author: stone
 * @date: Created by 2021/6/4 14:18
 * @version: 1.0.0
 * @pakeage: com.shiguiwu.springmybatis.spring.aop.principle.factory
 */
public class MyJdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
    private static final long serialVersionUID = 5531744639992436476L;
    private static final Log logger = LogFactory.getLog(MyJdkDynamicAopProxy.class);
    //代理的配置信息
    private final AdvisedSupport advised;
    //需要被代理的接口中是否定义了equals方法
    private boolean equalsDefined;
    //需要被代理的接口中是否定义了hashCode方法
    private boolean hashCodeDefined;

    //通过AdvisedSupport创建实例
    public MyJdkDynamicAopProxy(AdvisedSupport config) {

        Assert.notNull(config, "AdvisedSupport must not be null");
        if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
            throw new AopConfigException("No advisors and no TargetSource specified");
        } else {
            this.advised = config;
        }

    }


    /**
     * @param classLoader 生成一个代理对象
     * @return 返回代理对象
     */
    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        //根据advised的信息获取代理需要被代理的所有接口列表 @1
        Class<?>[] proxiedInterfaces = completeProxiedInterfaces(this.advised, true);
        //查找被代理的接口中是否定义了equals、hashCode方法
        this.findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        /*
         * 这个大家应该很熟悉吧，通过jdk动态代理创建代理对象，注意最后一个参数是this
         * 表示当前类，当前类是InvocationHandler类型的，当调用代理对象的任何方法的时候
         * 都会被被当前类的 invoke 方法处理
         */
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }

/**
 *
 * @param advised  代理配置信息
 * @param decoratingProxy  默认是true
 * @return  根据advised的信息获取代理需要被代理的所有接口列表
 */
static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
    //获取代理配置中需要被代理的接口
    Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
    //需要被代理的接口数量为0
    if (specifiedInterfaces.length == 0) {
        //获取需要被代理的目标类型
        Class<?> targetClass = advised.getTargetClass();
        //目标类型不为空
        if (targetClass != null) {
            //目标类型为接口
            if (targetClass.isInterface()) {
                //将其添加到需要代理的接口中
                advised.setInterfaces(new Class[]{targetClass});
                //目标类型为jdk动态代理创建的代理对象
            } else if (Proxy.isProxyClass(targetClass)) {
                //获取目标类型上的所有接口，将其添加到需要被代理的接口中
                advised.setInterfaces(targetClass.getInterfaces());
            }

            //再次获取代理配置中需要被代理的接口
            specifiedInterfaces = advised.getProxiedInterfaces();
        }
    }

    //判断SpringProxy接口是否已经在被代理的接口中
    boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);
    //判断Advised接口是否已经在被代理的接口中
    boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);
    //判断DecoratingProxy接口是否已经在被代理的接口中
    boolean addDecoratingProxy = decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class);
    //一个计数器，会根据上面三个boolean值做递增
    int nonUserIfcCount = 0;
    if (addSpringProxy) {
        ++nonUserIfcCount;
    }

    if (addAdvised) {
        ++nonUserIfcCount;
    }

    if (addDecoratingProxy) {
        ++nonUserIfcCount;
    }
    //下面就是构建所有需要被代理的接口,最新的接口个数 + 必须实现的三个默认接口
    Class<?>[] proxiedInterfaces = new Class[specifiedInterfaces.length + nonUserIfcCount];
    System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);
    int index = specifiedInterfaces.length;
    //下面的代码就简单了，将系统默认的三个接口赋值到数组中
    if (addSpringProxy) {
        proxiedInterfaces[index] = SpringProxy.class;
        ++index;
    }

    if (addAdvised) {
        proxiedInterfaces[index] = Advised.class;
        ++index;
    }

    if (addDecoratingProxy) {
        proxiedInterfaces[index] = DecoratingProxy.class;
    }

    //返回的是最终的代理实现的接口
    return proxiedInterfaces;
}


    /**
     * @param proxiedInterfaces 判断需要代理的接口中是否定义了这几个方法（equals、hashCode）
     */
    private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) {
        Class[] classes = proxiedInterfaces;
        int length = proxiedInterfaces.length;

        for (int i = 0; i < length; ++i) {
            Class<?> proxiedInterface = classes[i];
            //获取接口中定义的方法
            Method[] methods = proxiedInterface.getDeclaredMethods();
            int len = methods.length;

            for (int j = 0; j < len; ++j) {
                Method method = methods[j];
                //是否是equals方法
                if (AopUtils.isEqualsMethod(method)) {
                    //是的话给实例变量 equalsDefined 赋值
                    this.equalsDefined = true;
                }

                //是否是hashCode方法
                if (AopUtils.isHashCodeMethod(method)) {
                    //是的话给实例变量 hashCodeDefined 赋值
                    this.hashCodeDefined = true;
                }

                //如果发现这2个方法都定义了，结束循环查找，已有可能不同的接口定义的
                if (this.equalsDefined && this.hashCodeDefined) {
                    return;
                }
            }
        }

    }

    /**
     * 这个方法比较关键了，当在程序中调用代理对象的任何方法，最终都会被下面这个invoke方法处理
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //旧的代理对象
        Object oldProxy = null;
        //用来标记是否需要将代理对象暴露在ThreadLocal中
        boolean setProxyContext = false;
        //获取目标源
        TargetSource targetSource = this.advised.getTargetSource();
        //目标对象
        Object target = null;

        Class<?> clazz;
        //下面进入代理方法的处理阶段
        try {
            //处理equals方法：被代理的接口中没有定义equals方法 && 当前调用是equals方法
            if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
                //直接调用当前类中的equals方法
                return this.equals(args[0]);

            }

            //处理hashCode方法：被代理的接口中没有定义hashCode方法 && 当前调用是 hashCode方法
            else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
                //直接调用当前类中的hashCode方法
                return this.hashCode();
            }

            /*方法来源于 DecoratingProxy 接口，这个接口中定义了一个方法
            用来获取原始的被代理的目标类，主要是用在嵌套代理的情况下（所谓嵌套代理：代理对
            象又被作为目标对象进行了代理）
            */
            else if (method.getDeclaringClass() == DecoratingProxy.class) {
                return AopProxyUtils.ultimateTargetClass(this.advised);
            }

            // todo 方法来源于 Advised 接口，代理对象默认情况下会实现 Advised 接口，可以通过代理对象来动态向代理对象中添加通知等
            else if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                //调用AopProxyUtils工具类的方法，内部通过循环遍历的方式，找到最原始的被代 理的目标类
                return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
            }
            //用来记录方法返回值
            Object retVal;
            //是否需要将代理暴露在threadLocal中
            if (this.advised.isExposeProxy()) {
                /**
                 * 将代理对象暴露在上线文中，即暴露在threadLocal中，那么在当前线程中可以通
                 * 过静态方法AopContext#currentProxy获取当前被暴露的代理对象，这个是非常有用的，稍
                 * 后用案例来讲解，瞬间就会明白
                 */
                oldProxy = MyAopContext.setCurrentProxy(proxy);
                //将setProxyContext标记为true
                setProxyContext = true;
            }

            //通过目标源获取目标对象
            target = targetSource.getTarget();
            //获取目标对象类型
            Class<?> targetClass = target != null ? target.getClass() : null;

            //todo 获取当前方法的拦截器链@2
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

            //拦截器链为空的情况下，表示这个方法上面没有找到任何增强的通知，那么会直接通过反射直接调用目标对象
            if (chain.isEmpty()) {
                /*获取方法请求的参数（有时候方法中有可变参数，所谓可变参数就是带有省略号(...)这种格式的参数，传入的参数类型和这种类型不一样的时候，会通过下面的
                adaptArgumentsIfNecessary方法进行转换）*/
                Object[] argsToUse = adaptArgumentsIfNecessary(method, args);
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
            } else {
                //如有增强逻辑
                //创建一个方法调用器（包含了代理对象、目标对象、调用的方法、参数、目标类型、方法拦截器链）
                MethodInvocation invocation = null;//new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                //todo 通过拦截器链一个个调用最终到目标方法的调用@3
                retVal = invocation.proceed();
            }
            //下面会根据方法返回值的类型，做一些处理，比如方法返回的类型为自己，则最后需要将返回值置为代理对象
            Class<?> returnType = method.getReturnType();
            if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                retVal = proxy;
            } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
            }
            //返回方法调用结果
            return retVal;


        } finally {
            /**
             * 目标对象不为null && 目标源不是静态的  所谓静态的，你可以理解为是否是单例的
             *isStatic为true，表示目标对象是单例的，同一个代理对象中所有方法共享一个目标对象
             * isStatic为false的时候，通常每次调用代理的方法，target对象是不一样的，所以方
             * 法调用完之后需要进行释放，可能有些资源清理，连接的关闭等操作
             */
            if (target != null && !targetSource.isStatic()) {
                targetSource.releaseTarget(target);
            }

            //setProxyContext为ture 的情况
            if (setProxyContext) {
                //需要将旧的代理再放回到上线文中
                MyAopContext.setCurrentProxy(oldProxy);
            }

        }

    }

    static Object[] adaptArgumentsIfNecessary(Method method, @Nullable Object[] arguments) {
        if (ObjectUtils.isEmpty(arguments)) {
            return new Object[0];
        } else {
            if (!method.isVarArgs() || method.getParameterCount() != arguments.length) {
                return arguments;
            }
            //返回true如果这个可执行文件被宣布为带有可变数量的参数;返回false其他。

            Class<?>[] paramTypes = method.getParameterTypes();
            int varargIndex = paramTypes.length - 1;
            Class<?> varargType = paramTypes[varargIndex];
            if (varargType.isArray()) {
                Object varargArray = arguments[varargIndex];
                if (varargArray instanceof Object[] && !varargType.isInstance(varargArray)) {
                    Object[] newArguments = new Object[arguments.length];
                    System.arraycopy(arguments, 0, newArguments, 0, varargIndex);
                    Class<?> targetElementType = varargType.getComponentType();
                    int varargLength = Array.getLength(varargArray);
                    Object newVarargArray = Array.newInstance(targetElementType, varargLength);
                    System.arraycopy(varargArray, 0, newVarargArray, 0, varargLength);
                    newArguments[varargIndex] = newVarargArray;
                    return newArguments;
                }

            }
            return arguments;

        }
    }


    /**
     * @return 生成一个代理对象
     */
    @Override
    public Object getProxy() {
        return this.getProxy(ClassUtils.getDefaultClassLoader());
    }


}
